﻿'
' Boogaloo® - http://www.mindfly.com
' Copyright (c) 2008
' by Mindfly, Inc.
'
' Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
' documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
' the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
' to permit persons to whom the Software is furnished to do so, subject to the following conditions:
'
' The above copyright notice and this permission notice shall be included in all copies or substantial portions 
' of the Software.
'
' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
' INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
' DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
' SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
' SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
' WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
' THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Imports System
Imports System.IO
Imports System.Web
Imports System.Xml
Imports System.Text
Imports System.IO.Compression
Imports System.Text.RegularExpressions

Imports Boogaloo.Core.UI
Imports Boogaloo.Core.BLL

Namespace HttpHandlers


    Public Class CSSHandler
        Implements IHttpHandler


#Region "HttpHandler Stuff"

        Public Sub ProcessRequest(ByVal context As System.Web.HttpContext) Implements System.Web.IHttpHandler.ProcessRequest

            Dim enable_caching As Boolean = CoreSettings.Providers("theme").EnableCaching
            Dim admin_theme As String = CoreSettings.GetSetting("ADMIN_Theme")
            Dim th As Theme = Nothing
            If Not String.IsNullOrEmpty(admin_theme) AndAlso _
                context.Request.AppRelativeCurrentExecutionFilePath.Contains("~/App_Themes/" & admin_theme) Then
                ' This is a request for one of the admin pages
                th = New Theme(0, 0, CoreSettings.GetSetting("ADMIN_Theme"), Nothing, Nothing, Nothing)
            Else
                th = Groove.site_current_node.theme
            End If


            Dim css As String = String.Empty

            If th IsNot Nothing Then

                Dim key As String = "theme_theme_csshandler_theme_" & th.name

                If Not String.IsNullOrEmpty(context.Request.QueryString("cond")) Then
                    Dim cond As String = context.Request.QueryString("cond")

                    If th.manifest.bundle Then
                        key &= "_bundle_" & "_cond_" & cond

                        If context.Cache(key) IsNot Nothing And enable_caching Then
                            css = CType(context.Cache(key), String)
                        Else
                            css = th.manifest.GetConditionStylesheetGroupContentsByQueryStringGroupKey(cond)
                            If th.manifest.removewhitespace Then css = StripWhitespace(css)
                            context.Cache.Insert(key, css, New Caching.CacheDependency(th.manifest.path))
                        End If

                    End If

                ElseIf Not String.IsNullOrEmpty(context.Request.QueryString("stylesheet")) Then
                    Dim cssfile As String = context.Request.QueryString("stylesheet")

                    key &= "_cssfile_" & cssfile.Replace("/", String.Empty).Replace(".", String.Empty)

                    If context.Cache(key) IsNot Nothing And enable_caching Then
                        css = CType(context.Cache(key), String)
                    Else
                        css = GetLocalStyleSheetContents(cssfile)
                        If th.manifest.removewhitespace Then css = StripWhitespace(css)
                        If enable_caching Then context.Cache.Insert(key, css, New Caching.CacheDependency(th.manifest.path))
                    End If

                End If


                    If Not String.IsNullOrEmpty(css) Then


                        SetHeaders(css.GetHashCode, context)

                        context.Response.Write(css)

                        If th.manifest.compress Then Compress(context)

                    Else
                        context.Response.Status = "404 Bad Request"

                    End If


            End If




        End Sub


        ''' <summary>
        ''' Retrieves the contents of a local css file
        ''' </summary>
        Private Shared Function GetLocalStyleSheetContents(ByVal csshref As String) As String
            Return Groove.GetTextFileContents(HttpContext.Current.Server.MapPath(csshref))
        End Function

        ''' <summary>
        ''' retrieves cached css contents if available or returns string.empty
        ''' </summary>
        Private Shared Function GetCachedCss(ByVal key As String) As String
            Dim css As String = String.Empty
            Dim context As HttpContext = HttpContext.Current

            If context.Cache(key) IsNot Nothing Then
                css = CType(context.Cache(key), String)
            End If
            Return css
        End Function

        ''' <summary>
        ''' Removes whitespace in all stylesheets added to the
        ''' header of the HTML document in site.master.
        ''' </summary>
        ''' <remarks>Method directly translated from BlogEngine.NET</remarks>
        Private Shared Function StripWhitespace(ByVal body As String) As String

            body = body.Replace("  ", String.Empty)
            body = body.Replace(Environment.NewLine, String.Empty)
            body = body.Replace(vbTab, String.Empty)
            body = body.Replace(" {", "{")
            body = body.Replace(" :", ":")
            body = body.Replace(": ", ":")
            body = body.Replace(", ", ",")
            body = body.Replace("; ", ";")
            body = body.Replace(";}", "}")

            ' sometimes found when retrieving CSS remotely
            body = body.Replace("?", String.Empty)

            'body = Regex.Replace(body, "/\*[^\*]*\*+([^/\*]*\*+)*/", "$1");
            body = Regex.Replace(body, "(?<=[>])\s{2,}(?=[<])|(?<=[>])\s{2,}(?=&nbsp;)|(?<=&ndsp;)\s{2,}(?=[<])", String.Empty)

            'Remove comments from CSS
            body = Regex.Replace(body, "/\*[\d\D]*?\*/", String.Empty)

            Return body

        End Function



        ''' <summary>
        ''' Makes the browser and server keep the output in its cache and thereby improve performance
        ''' </summary>
        ''' <remarks>Method directly translated from BlogEngine.NET</remarks>
        Private Shared Sub SetHeaders(ByVal hash As Integer, ByVal context As HttpContext)

            With context.Response

                .ContentType = "text/css"
                .Cache.VaryByHeaders("Accept-Encoding") = True

                .Cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(7))
                .Cache.SetMaxAge(New TimeSpan(7, 0, 0, 0))
                .Cache.SetRevalidation(HttpCacheRevalidation.AllCaches)

                Dim etag As String = String.Format("""{0}""", hash.ToString)
                Dim incomingEtag As String = context.Request.Headers("If-None-Match")

                .Cache.SetETag(etag)

                If String.Compare(incomingEtag, etag) = 0 Then
                    .Clear()
                    .StatusCode = CType(System.Net.HttpStatusCode.NotModified, Integer)
                    .SuppressContent = True
                End If


            End With


        End Sub


        Private Const GZIP As String = "gzip"
        Private Const DEFLATE As String = "deflate"

        Private Shared Sub Compress(ByVal context As HttpContext)

            If context.Request.UserAgent IsNot Nothing Then
                If context.Request.UserAgent.Contains("MSIE 6") Then Exit Sub
            End If

            If IsEncodingAccepted(GZIP) Then
                context.Response.Filter = New GZipStream(context.Response.Filter, CompressionMode.Compress)
                SetEncoding(GZIP)

            ElseIf IsEncodingAccepted(DEFLATE) Then

                context.Response.Filter = New DeflateStream(context.Response.Filter, CompressionMode.Compress)
                SetEncoding(DEFLATE)
            End If



        End Sub

        ''' <summary>
        ''' Checks the request headers to see if the specified
        ''' encoding is accepted by the client.
        ''' </summary>
        ''' <remarks>Method directly translated from BlogEngine.NET</remarks>
        Private Shared Function IsEncodingAccepted(ByVal encoding As String) As Boolean

            Return HttpContext.Current.Request.Headers("Accept-encoding") IsNot Nothing _
                And HttpContext.Current.Request.Headers("Accept-encoding").Contains(encoding)
            
        End Function

        Private Shared Sub SetEncoding(ByVal encoding As String)
            HttpContext.Current.Response.AppendHeader("Content-encoding", encoding)
        End Sub

        Public ReadOnly Property IsReusable() As Boolean Implements System.Web.IHttpHandler.IsReusable
            Get
                Return False
            End Get
        End Property

#End Region
    End Class


End Namespace